package serveur;

import interfaces.IInvokedMethods;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Proxy;
import java.net.Socket;

import javassist.CannotCompileException;

import org.xml.sax.SAXException;

import reflexion.Reflexion;
import tools.CodeFaute;
import tools.GestionFautes;
import tools.XMLTool;

/**
 * Thread du client qui est lancé lors de sa connexion
 * 
 * @author Kevin Pozderec, Faycal Abka 
 *
 */
public class ClientThread implements Runnable {

	Socket sock;

	Thread th;

	GestionServeur gs;

	/** Flux sur lequel on écrit */
	PrintWriter fluxEcriture;

	/** Flux sur lequel on lit */
	BufferedReader fluxLecture;

	private int idSessionClient;

	public ClientThread(Socket sock, GestionServeur serveur) {
		this.sock = sock;

		try {

			/*Ouverture du flux d'écriture*/
			fluxEcriture = new PrintWriter(new BufferedWriter(
					new OutputStreamWriter(sock.getOutputStream())), true);
			/*Ouverture du flux de lecture*/
			fluxLecture = new BufferedReader(new InputStreamReader(
					sock.getInputStream()));
			gs = serveur;

			/* Ajout du flux de sortie du client dans la liste tenu a jour */
			idSessionClient = gs.ajouterClient(fluxEcriture);

		} catch (IOException e) {			
			/*on ne créer pas de methodresponse fault étant donné que le flux peut ne pas avoir été ouvert
			 * le client n'est donc pas en mesure d'obtenir une explication */
			e.printStackTrace();
		}

		th = new Thread(this);
		
		/*démarre le thread et la méthode run*/
		th.start();
	}

	@Override
	public void run() {

		String chaineLue;
		String reponse;
		String erreur_msg;
		
		Reflexion rfl;

		/* ce qui est fait dans le thread client */
			
		/*Lorsqu'on subit une exception coté serveur on renvoi un methodResponse de type fault avec le code et le message de l'exception*/
		
		try {
			/*Lecture flux*/
			chaineLue = fluxLecture.readLine();
		} catch (IOException e) {
			
			erreur_msg=GestionFautes.creerRetourErreur(e.getMessage(), CodeFaute.ERREUR_FLUX);
			System.err.println("Erreur : Problème de lecture sur le flux, envoi d'un methodresponse signalant l'erreur");
			closeConnexionErreur(erreur_msg);	
			
			return;			
		}

		try {
			/*Validation du xml reçu avec le Relax NG*/
			XMLTool.validerRelaxNG("annexes/xml-rmi.bin",chaineLue);
		} catch (SAXException e) {				
			/*RelaxNG non respecté : Envoi d'une methodResponse fault*/
					
			erreur_msg=GestionFautes.creerRetourErreur(e.getMessage(), CodeFaute.ERREUR_FLUX);
			System.err.println("Erreur : RelaxNG non respecté, envoi d'un methodresponse signalant l'erreur");
			closeConnexionErreur(erreur_msg);
				
			return;
				
		} catch (IOException e) {
			/*Problème de lecture du fichier RelaxNG : Envoi d'une methodResponse fault*/
			
			erreur_msg=GestionFautes.creerRetourErreur(e.getMessage(), CodeFaute.ERREUR_FICHIER);
			System.err.println("Erreur : Problème de lecture du fichier RelaxNG, envoi d'un methodresponse signalant l'erreur");
			closeConnexionErreur(erreur_msg);
				
			return;
		}

		/*Utilisation d'un proxy pour l'utilisation de méthode parmi une liste que l'on tient à jour dans IInvokedMethod*/
		IInvokedMethods im = (IInvokedMethods) Proxy
				.newProxyInstance(IInvokedMethods.class.getClassLoader(),
						new Class[] { IInvokedMethods.class },
						new InvokedMethods());

		rfl = new Reflexion();
			
		try {

			/*Instancie les objets nécessaire à l'utilisation de la méthode coté serveur*/
			rfl.getObjectsFromXML(chaineLue);
			/*Lance l'appel de la méthode et récupère en résultat une chaîne xml de type XML RMI*/
			reponse = CallMethod.call(rfl.getMethodName(),rfl.getObjets(),
					rfl.getInternes(),  rfl.getParams(),
					rfl.getCp(), im);
			
			fluxEcriture.write(reponse);
			fluxEcriture.flush();
			
		} catch (InstantiationException e) {
			
			/*Problème d'instanciation des arguments ; Envoi d'une methodResponse fault*/
			
			erreur_msg=GestionFautes.creerRetourErreur(e.getMessage(),CodeFaute.ERREUR_JAVASSIST_INSTANCIATION);
			System.err.println("Erreur : Problème à l'instanciation des arguments par Javassist, envoi d'un methodresponse signalant l'erreur");

			closeConnexionErreur(erreur_msg);
				
			return;
				
		} catch (IllegalAccessException e) {
				
			/*Problème d'accès sur une méthode ou un champs (problème de visibilité?) ; Envoi d'une methodResponse fault*/
			erreur_msg=GestionFautes.creerRetourErreur(e.getMessage(),CodeFaute.ERREUR_JAVASSIST_ACCES);
			System.err.println("Erreur : Problème d'accès à un champs ou une méthode par Javassist, envoi d'un methodresponse signalant l'erreur");
				
			closeConnexionErreur(erreur_msg);
				
			return;
				
		} catch (CannotCompileException e) {
			
			/*Problème de compilation Javassist; Envoi d'une methodResponse fault*/
			erreur_msg=GestionFautes.creerRetourErreur(e.getMessage(),CodeFaute.ERREUR_JAVASSIST_COMPILATION);
			System.err.println("Erreur : Problème de compilation d'une classe instanciée par Javassist, envoi d'un methodresponse signalant l'erreur");				
			closeConnexionErreur(erreur_msg);
				
			return;
			
		}	
		
		/*retire le client de la liste des clients connectés au serveur*/
		gs.retirerClient(idSessionClient);

		try {
			sock.close();
			System.out.println("Fin de connexion du client d'id :"+  idSessionClient);
		} catch (IOException e) {
			/*Problème serveur, on n'envoie pas de résultat au client*/
			e.printStackTrace();
		}
	}
	
	/**
	 * Méthode pour clotûrer la connexion et envoyer un message d'erreur de type methodResponse fault
	 * @param err_msg : methodResponse
	 */
	public void closeConnexionErreur(String err_msg){
		
		fluxEcriture.write(err_msg);
		fluxEcriture.flush();
		
		gs.retirerClient(idSessionClient);
		
		try {
			sock.close();
			System.out.println("Fin de connexion du client d'id :"+  idSessionClient);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
